# elliptical_chuck.py

from __future__ import division

import math

from ot_simulator.component import component

class elliptical_chuck(component.component):
    '''This chuck cuts an ellipse onto the work.
    
    The chuck spindle describes a circle twice as the chuck turns through one
    revolution.  The circle has a diameter of ``e`` centered at ``(e/2, 0)``.

    ===============  ========================================================
     Input setting   
    ===============  ========================================================
    index (degrees)  from the headstock
    e                a - b for the ellipse being cut
    nosewheel_index  degrees, defaults to 0
    sr               cutter offset (only needed if *tangent* is asked for)
    ===============  ========================================================

    =================  ========================================================
    Output setting
    =================  ========================================================
    h                  slide offset of chuck spindle relative to chuck
                       coordinates X-axis.  (could be + or -).
    tangent (degrees)  angle tangent to ellipse from vertical.  (Must also
                       specify the *sr* input parameter).
    =================  ========================================================
    '''
    input_parameters = ('index', 'e', 'nosewheel_index', 'sr')

    def h(self, params):
        r'''The offset of the chuck's nosewheel from the lathe center.

        h = e * cos(index)
        '''
        theta = math.radians(self.get_param(params, 'index'))
        e = float(self.get_param(params, 'e'))
        h = e * math.cos(theta)
        self.set_param(params, 'h', h)
        return h

    def tangent(self, params):
        r'''The angle (in degrees) tangent to the ellipse where it is being cut.

        From http://en.wikipedia.org/wiki/Ellipse#Parametric_form_in_canonical_position
        the parametric equations for an ellipse in canonical position are:
            X(t) = a * cos(t)
            Y(t) = b * sin(t)
        where a and b are the semi_major and semi_minor axes.

        Thus, the headstock index (hi) is 180 - t, so that:
            sin(hi) == sin(t) and
           -cos(hi) == cos(t)

        Then the tangent is dy/dt over dx/dt plus the headstock.index (which
        rotates the ellipse):
            dy/dt = b * cos(t)
            dx/dt = -a * sin(t)
        or:
            dy/dt = -b * cos(hi)
            dx/dt = -a * sin(hi)

        So the angle tangent to the ellipse is:
            atan2(-b * cos(hi), -a * sin(hi)) + hi

        This comes out -90 +/- the angle of the surface of the ellipse.  So we
        add 90 to it so that it is centered around 0.  This is the angle from
        the vertical, rotated around the ornamental lathe's Z axis.

        NOTE: the actual angle returned is sometimes, say, -10 and other times
        350 depending on the headstock index.  So be prepared for both!
        '''
        sr = self.get_param(params, 'sr')
        e = self.get_param(params, 'e')
        index = self.get_param(params, 'index')
        index_radians = math.radians(index)
        s, c = math.sin(index_radians), math.cos(index_radians)
        tangent = math.degrees(math.atan2(-sr * c, -(sr + e) * s)) \
                  + index \
                  + 90.0
        self.set_param(params, 'tangent', tangent)
        return tangent

    def get_transform(self, params, transform_in):
        h = self.get_param(params, 'h')
        transform_out = transform_in.move(x=h)
        nw_index = self.get_param(params, 'nosewheel_index', 0)
        if nw_index:
            transform_out = transform_out.rotate_z(nw_index)
        return transform_out


def test():
    import doctest
    import sys
    sys.exit(doctest.testmod()[0])

if __name__ == "__main__":
    test()
